/*
    Dominate 3D - A cube-based game of life

    Copyright (C) 2007, Craig H. Miller (kidmosey@gmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "cube.h"
#include "cell.h"
#include "player.h"
#include "util.h"

#include <algorithm>
#include <vector>
#include <assert.h>

#ifdef _MSC_VER
#pragma warning(disable: 4305 4267 4244)
#endif

Cube::Cube(): m_selectedCell(0), m_isFull(false)
{
    m_timerStart    = 0.0f;
    m_currentPlayer = m_listPlayers.begin();

    Cell::ResetIDS();

    InitVertices();
    CalculateNeighbors();
}
/*
Cube::Cube(const Cube &copy)
{
    m_matRotation = copy.m_matRotation;
    m_listPlayers = copy.m_listPlayers;
    m_currentPlayer = m_listPlayers.begin();
    memcpy(m_listVerts, copy.m_listVerts, sizeof(m_listVerts));

    std::vector<Cell *>::const_iterator i = copy.m_listCells.begin();
    while (i != copy.m_listCells.end())
    {
        m_listCells.push_back(new Cell(*(*i)));
        *i++;
    }
}
*/

Cube::~Cube()
{
    std::for_each(m_listCells.begin(), m_listCells.end(), DeleteObject<Cell>);
    m_listCells.clear();
}

Cube::Cube(int dimXY): m_selectedCell(0), m_isFull(false)
{
    assert (dimXY > 0 );
    assert (dimXY < 24);
    m_currentPlayer = m_listPlayers.begin();

    Cell::ResetIDS();

    InitVertices();
    Generate(dimXY);
}

#include <GL/glfw.h>
void Cube::Update()
{
    static float prev_time = 0.0f;

    float dt = glfwGetTime() - prev_time;
    if (dt > 1.0f)
        dt = 0.0f;

    ApplyRotation(m_vRotVelocity.toRadians() * (dt * 2.0f));

    prev_time = glfwGetTime();
}

void Cube::SetCurrentPlayer(int id)
{
    m_timerStart = glfwGetTime();

    std::vector<Player *>::iterator i = m_listPlayers.begin();
    while (i != m_listPlayers.end())
    {
        if ((*i)->GetID() == id)
            break;

        *i++;
    }

    m_currentPlayer = i;
    if (m_currentPlayer == m_listPlayers.end())
        m_isFull = true;
}

float Cube::GetTimeElapsed() const
{
    return glfwGetTime() - m_timerStart;
}

void Cube::IncrementPlayer()
{
    size_t count = m_listPlayers.size();

    if (m_currentPlayer != m_listPlayers.end())
    {
        (*m_currentPlayer)->m_numTurns++;
        (*m_currentPlayer)->m_totalTime += GetTimeElapsed();
        m_timerStart = glfwGetTime();

        // cycle through the players once
        // pre-increment to avoid re-choosing the current player.
        while (--count)
        {
            // increment the player
            *m_currentPlayer++;
            if (m_currentPlayer == m_listPlayers.end())
                m_currentPlayer = m_listPlayers.begin();

            // see if this player can move.
            if (CanMove(*m_currentPlayer))
                return;
        }
    }

    // nobody can move, so freeze the cube.
    m_currentPlayer = m_listPlayers.end();
    m_isFull = true;
}

bool Cube::IsFull() const
{
    return m_isFull;
}

std::vector<Cell *> Cube::GetStartCells(Player *player)
{
    std::vector<Cell *> result;

    int idx = 0;
    std::vector<Player *>::iterator i = m_listPlayers.begin();
    while (i != m_listPlayers.end())
    {
        if (*i == player) break;

        idx++;
        *i++;
    }

    std::vector<Cell *> cells = GetCellsAt(m_listVerts[idx/3]);
    Cell *cell = cells[idx % 3];
    result.push_back(cell);

    std::set<Cell *> surr = cell->GetSurroundingCells();
    std::set<Cell *>::iterator c = surr.begin();
    while (c != surr.end())
    {
        if ((*c)->GetNormal() == cell->GetNormal())
            result.push_back(*c);

        *c++;
    }

    return result;
}

void Cube::RemovePlayerCells(Player *p)
{
    std::vector<Cell *>::iterator i = m_listCells.begin();
    while (i != m_listCells.end())
    {
        if ((*i)->GetPlayer() == p)
            (*i)->SetPlayer(&Player::nullPlayer);

        *i++;
    }
}

void Cube::RemovePlayer(int id)
{
	Player *currPlayer = 0;

    std::vector<Player *>::iterator i = m_listPlayers.begin();
    while (i != m_listPlayers.end())
    {
        if ((*i)->GetID() == id)
        {
            if (*i == *m_currentPlayer)
            {
				// save the next player for later
                IncrementPlayer();
				if (m_currentPlayer != m_listPlayers.end())
					currPlayer = *m_currentPlayer;

				// deselect all the cells
                if (m_selectedCell)
                    m_selectedCell->Unselect();

				m_selectedCell = 0;
            }

            RemovePlayerCells(*i);
            delete (*i);
            m_listPlayers.erase(i);

            break;
        }

        *i++;
    }

	// reset our current player
	if (currPlayer)
		m_currentPlayer = std::find(m_listPlayers.begin(), m_listPlayers.end(), currPlayer);
}

Player *Cube::GetCurrentPlayer()
{
    if (m_currentPlayer == m_listPlayers.end())
        return 0;

    return *m_currentPlayer;
}

Cell *Cube::GetCell(int cellID)
{
    std::vector<Cell *>::iterator i = m_listCells.begin();
    while (i != m_listCells.end())
    {
        if ((*i)->GetID() == cellID)
            return (*i);
        *i++;
    }

    // This should not be hit (unless player is malicious)
    //assert(i != m_listCells.end());

    return 0;
}

int Cube::CountSurroundingCells(Cell *cell, std::map<Player *, int> &counts) const
{
    assert(cell);

    int total = 0;

    for (size_t i = 0; i < m_listPlayers.size(); i++)
    {
        assert(m_listPlayers[i]);

        int count = cell->CountSurroundingCells(m_listPlayers[i]);
        counts[m_listPlayers[i]] = count;

        total += count;
    }

    return total;
}

    Player *Cube::GetPlayer(int id)
    {
        if (id == 0)
            return &Player::nullPlayer;

        std::vector<Player *>::iterator iter = m_listPlayers.begin();
        while (iter != m_listPlayers.end())
        {
            assert (*iter);
            if ((*iter)->GetID() == id)
                return (*iter);

            *iter++;
        }

        if (id == -1)
            return 0;

        //assert(iter != m_listPlayers.end());
        return 0;
    }

std::vector<CellStats> Cube::GetCellStats(Player *player) const
{
    std::vector<CellStats> result;

    std::vector<Cell *>::const_iterator i = m_listCells.begin();
    while (i != m_listCells.end())
    {
        assert(*i);

        if ((*i)->GetPlayer() == player)
        {
            Cell::HighlightList hl_list = (*i)->Select();

            Cell::HighlightList::iterator j = hl_list.begin();
            while (j != hl_list.end())
            {
                assert((*j).first);
                if ((*j).first->IsEmpty())
                {
                    CellStats stats;

                    stats.moveFrom          = *i;
                    stats.moveTo            = (*j).first;
                    stats.moveType          = (*j).second;

                    stats.cells_vulnerable_move = 0;
                    if (stats.moveType == Cell::JUMP_TO)
                        stats.cells_vulnerable_move = (*i)->CountSurroundingCells(player);

                    stats.cells_vulnerable_nomove = (*j).first->CountSurroundingCells(player);

                    stats.total_cells_taken = CountSurroundingCells((*j).first, stats.cells_taken);

                    result.push_back(stats);
                }
                *j++;
            }

            (*i)->Unselect();
        }

        *i++;
    }

    return result;
}

const CMatrix &Cube::GetMatrix() const
{
    return m_matRotation;
}

void Cube::Render() const
{
    glPushMatrix();

    glTranslatef(0,-0.25,0);
    glMultMatrixf((GLfloat *)&GetMatrix());

    std::for_each(m_listCells.begin(), m_listCells.end(),
                  std::mem_fun( &Cell::Render ));

    if (m_selectedCell)
        m_selectedCell->RenderHighlight();

    glPopMatrix();
}

void Cube::GenerateFace(const CVector3D &vecNormal, const CVector3D &vecRight, const CVector3D &vecDown, float delta)
{
    CVector3D vecPosNormal = vecNormal * 0.5f;

    CVector3D vec_i = -(vecDown * 0.5f);
    for (float i = -0.5f; i < 0.5f; i += delta)
    {
        CVector3D vec_j = -(vecRight * 0.5f);
        for (float j = -0.5f; j < 0.5f; j += delta)
        {
            m_listCells.push_back(new Cell(
                vec_j + vec_i + vecPosNormal,
                vec_j + (vecRight * delta) + vec_i + vecPosNormal,
                vec_j + (vecRight * delta) + vec_i + (vecDown  * delta) + vecPosNormal,
                vec_j + vec_i + (vecDown  * delta) + vecPosNormal
            ));
            vec_j += (vecRight * delta);
        }
        vec_i += (vecDown * delta);
    }
}

void Cube::Generate(int dimXY)
{
    std::for_each(m_listCells.begin(), m_listCells.end(), DeleteObject<Cell>);
    m_listCells.clear();

    float delta = 1.0f / (float )dimXY;

    // Front face
    GenerateFace(
        CVector3D( 0.0f, 0.0f,-1.0f), // vecNormal
        CVector3D( 1.0f, 0.0f, 0.0f), // vecRight
        CVector3D( 0.0f,-1.0f, 0.0f), // vecDown
        delta
    );

    // Back face
    GenerateFace(
        CVector3D( 0.0f, 0.0f, 1.0f), // vecNormal
        CVector3D(-1.0f, 0.0f, 0.0f), // vecRight
        CVector3D( 0.0f,-1.0f, 0.0f), // vecDown
        delta
    );

    // Top face
    GenerateFace(
        CVector3D( 0.0f, 1.0f, 0.0f), // vecNormal
        CVector3D( 1.0f, 0.0f, 0.0f), // vecRight
        CVector3D( 0.0f, 0.0f,-1.0f), // vecDown
        delta
    );

    // Bottom face
    GenerateFace(
        CVector3D( 0.0f,-1.0f, 0.0f), // vecNormal
        CVector3D(-1.0f, 0.0f, 0.0f), // vecRight
        CVector3D( 0.0f, 0.0f,-1.0f), // vecDown
        delta
    );

    // Left face
    GenerateFace(
        CVector3D(-1.0f, 0.0f, 0.0f), // vecNormal
        CVector3D( 0.0f, 0.0f,-1.0f), // vecRight
        CVector3D( 0.0f,-1.0f, 0.0f), // vecDown
        delta
    );

    // Right face
    GenerateFace(
        CVector3D( 1.0f, 0.0f, 0.0f), // vecNormal
        CVector3D( 0.0f, 0.0f, 1.0f), // vecRight
        CVector3D( 0.0f,-1.0f, 0.0f), // vecDown
        delta
    );

    CalculateNeighbors();
}

void Cube::Subdivide()
{
    int count = m_listCells.size();
    for (int i = 0; i < count; i++)
    {
        Cell *c = m_listCells[i];
        assert(c);

        // subdivide this cell
        std::vector<Cell *> result = c->Subdivide();

        // append the subdivisions to the end of the list
        std::copy(result.begin(), result.end(), back_inserter(m_listCells));

        // remove the old cell
        delete c;
    }

    m_listCells.erase(m_listCells.begin(), m_listCells.begin() + count);

    CalculateNeighbors();
}

void Cube::CalculateNeighbors()
{
    std::vector<Cell *>::iterator i = m_listCells.begin();
    while (i != m_listCells.end())
    {
        AttachNeighbors(*i);
        *i++;
    }

    std::for_each(m_listCells.begin(), m_listCells.end(),
                  std::mem_fun(&Cell::InitSurroundingCells)
                 );
}

void Cube::ResetRotation()
{
    m_matRotation.Identity();
}

void Cube::ApplyRotation(const CVector3D &rot)
{
    CMatrix matX, matY, matZ;

    matX.MakeRotateX(rot.x);
    matY.MakeRotateY(rot.y);
    matZ.MakeRotateZ(rot.z);

    m_matRotation *= matX * matY * matZ;
}

std::vector<Cell *> Cube::GetCellsAt(const CVector3D &v)
{
    std::vector<Cell *> result;

    std::vector<Cell *>::iterator i = m_listCells.begin();
    while (i != m_listCells.end())
    {
        assert((*i));

        if ((*i)->Contains(v))
            result.push_back(*i);

        *i++;
    }

    return result;
}
/*
void Cube::AddPlayers(std::vector<Player *> &players)
{
    //copy(players.begin(), players.end(), std::back_inserter(m_listPlayers));
    m_listPlayers = players;

    std::vector<Player *>::iterator currPlayer = players.begin();

    // adding players to the faces
    if (8 % players.size())
    {
        int offset = (m_listCells.size() / 6) / 2;

        for (int i = 0; i < 6; i++)
        {
            Cell *c = m_listCells[offset];
            assert(c);

            c->SetPlayer(*currPlayer);
            c->GetNeighbor(0)->SetPlayer(*currPlayer);
            c->GetNeighbor(1)->SetPlayer(*currPlayer);
            c->GetNeighbor(1)->GetNeighbor(0)->SetPlayer(*currPlayer);

            offset += (m_listCells.size() / 6);

            if (++currPlayer == players.end())
                currPlayer = players.begin();
        }
    }
    // adding players to the vertices
    else
    {
        for (int i = 0; i < 8; i++)
        {
            std::vector<Cell *> cells = GetCellsAt(m_listVerts[i]);
            std::for_each(cells.begin(), cells.end(),
                          std::bind2nd(std::mem_fun(&Cell::SetPlayer), *currPlayer)
                         );

            if (++currPlayer == players.end())
                currPlayer = players.begin();
        }
    }
}
*/
bool Cube::CanMove(Player *p)
{
    std::vector<Cell *>::iterator i = m_listCells.begin();
    while (i != m_listCells.end())
    {
        assert((*i));

        if ((*i)->GetPlayer() == p)
        {
            if ((*i)->CanMove())
                return true;
        }

        *i++;
    }

    return false;
}

void Cube::InitVertices()
{
    // 8 vertices for the cube
    CVector3D v[] =
    {
        CVector3D(-0.5,-0.5, 0.5),
        CVector3D( 0.5,-0.5, 0.5),
        CVector3D( 0.5, 0.5, 0.5),
        CVector3D(-0.5, 0.5, 0.5),
        CVector3D(-0.5,-0.5,-0.5),
        CVector3D( 0.5,-0.5,-0.5),
        CVector3D( 0.5, 0.5,-0.5),
        CVector3D(-0.5, 0.5,-0.5)
    };
    memcpy(m_listVerts, v, sizeof(v));

    // quad indices for the sides
    int indices[] =
    {
        0,1,2,3,//0, 3, 2, 1,
        4,5,1,0,//4, 0, 1, 5,
        7,6,5,4,//7, 4, 5, 6,
        3,2,6,7,//3, 7, 6, 2,
        4,0,3,7,//4, 7, 3, 0,
        1,5,6,2 //1, 2, 6, 5
    };

    // Initialize the list of cells for this cube
    for (unsigned int i = 0; i < sizeof(indices)/sizeof(*indices); i += 4)
    {
        m_listCells.push_back(new Cell(
                                  v[indices[i  ]],
                                  v[indices[i+1]],
                                  v[indices[i+2]],
                                  v[indices[i+3]]
                              ));
    }
}

void Cube::AttachNeighbors(Cell *cell)
{
    assert(cell);

    CVector3D normal = cell->GetNormal();
    CVector3D mid    = (cell->GetVert(0) + cell->GetVert(2)) * 0.5f;

    Edge edges[] =
    {
        Edge(cell->GetVert(0), cell->GetVert(1)),
        Edge(cell->GetVert(1), cell->GetVert(2)),
        Edge(cell->GetVert(2), cell->GetVert(3)),
        Edge(cell->GetVert(3), cell->GetVert(0))
    };

    std::vector<Cell *>::iterator i = m_listCells.begin();
    while (i != m_listCells.end())
    {
        Cell *cell_b = *i;
        assert(cell_b);

        if (cell == cell_b)
        {
            *i++;
            continue;
        }

        Edge edges_b[] =
        {
            Edge(cell_b->GetVert(0), cell_b->GetVert(1)),
            Edge(cell_b->GetVert(1), cell_b->GetVert(2)),
            Edge(cell_b->GetVert(2), cell_b->GetVert(3)),
            Edge(cell_b->GetVert(3), cell_b->GetVert(0))
        };

        for (int j = 0; j < 4; j++)
            for (int k = 0; k < 4; k++)
            {
                if (edges[j] == edges_b[k])
                {
                    cell  ->SetNeighbor(j, cell_b);
                    cell_b->SetNeighbor(k, cell  );
                }
            }

        if (-normal == cell_b->GetNormal())
        {
            CVector3D mid_b = (cell_b->GetVert(0) + cell_b->GetVert(2)) * 0.5f;
            if ((mid - mid_b).normal() == normal)
            {
                cell  ->SetNeighbor(4, cell_b);
                cell_b->SetNeighbor(4, cell  );
            }
        }

        *i++;
    }
}
